#include <stdio.h>
#include <stdlib.h>
#include "primevalvm.h"

/*##############################################################################
#                                                                              #
# ENTRYPOINT                                                                   #
#                                                                              #
##############################################################################*/
int main(int argc, char **argv)
{
    if ( argc < 2 ) {
        usageError();
        return 1;
    }
    if ( argc == 3 ) {
        if ( strcmp(argv[1], "d") == 0 ) {
            usageError();
            return 1;
        } else {
            if ( strcmp(argv[2], "d") == 0 ) {
                debug = TRUE;
            } else {
                debug = FALSE;
            }
        }
    }
    char op, a, b;
    int c;

    initialize();
    if ( readExecutableFileToMemory(argv[1]) ) {
        while ( run ) {          
            fetchInstruction();
            decodeInstruction(&op, &a, &b, &c);
            executeInstruction(&op, &a, &b, &c);
            if ( debug ) {
                printStatus();
            }
        }
        return 0;
    } else {
        return 1;
    }
}

void usageError() {
    printf("\nPRIMEVAL: usage \"primevalvm <file> [d]\"\n");
}

/*##############################################################################
#                                                                              #
# INITIALIZE                                                                   #
#                                                                              #
##############################################################################*/
void initialize() {
    programCounter = 0;
    memory = (unsigned char*) malloc(MEMORYSIZE * sizeof(char));
    reg = (long*) malloc(REGISTERSIZE * sizeof(long));
    run = TRUE;

    int i;
    for ( i = 0; i < REGISTERSIZE; i++ ) {
        reg[i] = 0;
    }
    reg[STACKPOINTER] = MEMORYSIZE - 1;
}

/*##############################################################################
#                                                                              #
# READ FILE TO MEMORY                                                          #
#                                                                              #
##############################################################################*/
int readExecutableFileToMemory(char *fileName) {
    FILE *file = fopen(fileName, "rb");
    if ( !file ) {
        printf("PRIMEVAL> ERROR: cannot open file \"%s\"\n", fileName);
        return FALSE;
    }
    fseek(file, 0, SEEK_END);
    programSize = ftell(file) - sizeof(unsigned long);
    fseek(file, 0, SEEK_SET);
    fread(memory, 1, programSize, file);
    fclose(file);
    return TRUE;
}

/*##############################################################################
#                                                                              #
# FETCH                                                                        #
#                                                                              #
##############################################################################*/
void fetchInstruction() {
    instructionRegister = 0;
    int i;
    for ( i = 3; i >= 0; i-- ) {
        instructionRegister |= memory[(programCounter) + i];
        if ( i > 0 ) {
            instructionRegister <<= 8;
    }   }
}

/*##############################################################################
#                                                                              #
# DECODE                                                                       #
#                                                                              #
##############################################################################*/
int decodeInstruction(char *op, char *a, char *b, int *c) {

    *op = ( instructionRegister >> 26 ) & 0x3F;

    if ( *op >= 1 && *op <= 33 ) {    // if Format 0 or Format 1
        *a = ( instructionRegister >> 21 ) & 0x1F;
        *b = ( instructionRegister >> 16 ) & 0x1F;

        if ( *op >= 1 && *op <= 22 ) {   // if Format 0
            *c = instructionRegister & 0xFFFF;
            if ( *c >= 32768 ) {
                *c -= 65536;
            }
        }
        else {  // if Format 1
            *c = instructionRegister & 0x1F;
        }
        return TRUE;
    }
    else if ( *op == 34 ) {   // if Format 2
        *c = instructionRegister & 0x3FFFFFF;
        return TRUE;
    }
    else {
        return FALSE;
    }
}

/*##############################################################################
#                                                                              #
# EXECUTE                                                                      #
#                                                                              #
##############################################################################*/
void executeInstruction(char *op, char *a, char *b, int *c) {
    if ( debug ) {
        printf("\n%s(%d, %d, %d) @ %d -> REGISTER-STATUS:\n", getInstructionString(*op),
                                                              *a, *b, *c , programCounter/4);
    }

    int nextProgramCounter = programCounter + 4;

    switch ( *op ) {
        case ADDI :
            reg[*a] = reg[*b] + *c;
            break;

        case SUBI :
            reg[*a] = reg[*b] - *c;
            break;

        case MULI :
            reg[*a] = reg[*b] * *c;
            break;

        case DIVI :
            reg[*a] = reg[*b] / *c;
            break;

        case MODI :
            reg[*a] = reg[*b] % *c;
            break;

        case CMPI :
            reg[*a] = reg[*b] - *c;
            break;

        case ADD :
            reg[*a] = reg[*b] + reg[*c];
            break;

        case SUB :
            reg[*a] = reg[*b] - reg[*c];
            break;

        case MUL :
            reg[*a] = reg[*b] * reg[*c];
            break;

        case DIV :
            reg[*a] = reg[*b] / reg[*c];
            break;

        case MOD :
            reg[*a] = reg[*b] % reg[*c];
            break;

        case CMP :
            reg[*a] = reg[*b] - reg[*c];
            break;

        case LDW :
            reg[*a] = memory[ programSize + ( (reg[*b] + *c) / 4 ) ];
            break;

        case STW :
            memory[ programSize + ( (reg[*b] + *c) / 4 ) ] = reg[*a];
            break;

        case POP :
            reg[*a] = memory[reg[*b] / 4];
            reg[*b] += *c;
            break;

        case PSH :
            reg[*b] -= *c;
            memory[reg[*b] / 4] = reg[*a];
            break;

        case BEQ :
            if ( reg[*a] == 0 ) {
                nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BGE :
            if ( reg[*a] >= 0 ) {
                nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BGT :
            if ( reg[*a] > 0 ) {
                nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BLE :
            if ( reg[*a] <= 0 ) {
               nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BLT :
            if ( reg[*a] < 0 ) {
                nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BNE :
            if ( reg[*a] != 0 ) {
                nextProgramCounter = programCounter + *c * 4;
            }
            break;

        case BR :
            nextProgramCounter = programCounter + *c * 4;
            break;

        case BSR :
            reg[LINKREGISTER] = programCounter + 4;
            nextProgramCounter = programCounter + *c * 4;
            break;

        case RET :
            nextProgramCounter = reg[*c];
            break;

        case JSR :
            reg[LINKREGISTER] = programCounter + 4;
            nextProgramCounter = *c;
            break;

        case PRINTC :
            printf("PRIMEVAL> %c\n", (char) reg[*a]);
            break;

        case READC :
            printf("PRIMEVAL> enter char ");
            scanf("%c", &reg[*a]);
            break;

        case PRINTI :
            printf("PRIMEVAL> %d\n", (int) reg[*a]);
            break;

        case READI :
            printf("PRIMEVAL> enter integer ");
            scanf("%d", &reg[*a]);
            break;

        case TRAP :
            run = FALSE;
            break;

        default:
            run = FALSE;
            printf("PRIMEVAL> ERROR: invalid opcode\n");
    }

    *op = -1; *a = 0; *b = 0; *c = 0;
    if ( nextProgramCounter <= 0 ) {
        programCounter = -1;
    } else {
        programCounter = nextProgramCounter;
    }
}

/*##############################################################################
#                                                                              #
# PRINTSTATUS                                                                  #
#                                                                              #
##############################################################################*/
void printStatus() {
    int i;
    for ( i = 0; i < REGISTERSIZE - 1; i++ ) {
        printf("R[%d]=%d; ", i, reg[i]);
        if ( i % 8 == 0 && i != 0 ) {
            printf("\n");
        }
    }
    printf("R[%d]=%d\n", REGISTERSIZE - 1, reg[REGISTERSIZE - 1]);
}

char *getInstructionString(char op) {
    switch ( op ) {
        case ADDI :
            return "ADDI";
        case SUBI :
            return "SUBI";
        case MULI :
            return "MULI";
        case DIVI :
            return "DIVI";
        case MODI :
            return "MODI";
        case CMPI :
            return "CMPI";
        case ADD :
            return "ADD";
        case SUB :
            return "SUB";
        case MUL :
            return "MUL";
        case DIV :
            return "DIV";
        case MOD :
            return "MOD";
        case CMP :
            return "CMP";
        case LDW :
            return "LDW";
        case STW :
            return "STW";
        case POP :
            return "POP";
        case PSH :
            return "PSH";
        case BEQ :
            return "BEQ";
        case BGE :
            return "BGE";
        case BGT :
            return "BGT";
        case BLE :
            return "BLE";
        case BLT :
            return "BLT";
        case BNE :
            return "BNE";
        case BR :
            return "BR";
        case BSR :
            return "BSR";
        case RET :
            return "RET";
        case JSR :
            return "JSR";
        case PRINTC :
            return "PRINTC";
        case READC :
            return "READC";
        case PRINTI :
            return "PRINTI";
        case READI :
            return "READI";
        case TRAP :
            return "TRAP";
        default:
            return "INVALID";
    }
}
